using System;
using System.Collections.Generic;
using System.Text;
using Wrath.Xml;
using System.Collections;

namespace Wrath.CodeBox
{
    public class Production
    {
        public string Name;
        public bool Indents;
        public PatternList Pattern;

        public override string ToString()
        {
            return Pattern.ToString();
        }

        public string Xml
        {
            get
            {
                if (Indents)
                {
                    return "  <production name='" + Element.XmlEncode(Name, false) + "' indents='true'>" + Element.XmlEncode(ToString(), false) + "</production>";
                }
                else
                {
                    return "  <production name='" + Element.XmlEncode(Name, false) + "'>" + Element.XmlEncode(ToString(), false) + "</production>";
                }
            }
            set
            {
                // replace all runs of whitespace with a single space
                value = System.Text.RegularExpressions.Regex.Replace(value, "([ \t\n\r]{2,})", " ");

                // parse the xml
                try
                {
                    FileParser parser = new FileParser(value);
                    Element productionElement = parser.FindFirstElement("production");
                    if (productionElement != null)
                    {
                        Wrath.Xml.Attribute nameAttribute = productionElement.FindAttribute("name");
                        if (nameAttribute != null)
                        {
                            Name = nameAttribute.Value;
                        }

                        Wrath.Xml.Attribute indentsAttribute = productionElement.FindAttribute("indents");
                        if (indentsAttribute != null)
                        {
                            if (!bool.TryParse(indentsAttribute.Value, out Indents))
                            {
                                Indents = false;
                            }
                        }
                        else
                        {
                            Indents = false;
                        }

                        Pattern.FromString(productionElement.Value);
                    }
                }
                catch (Exception ex)
                {
                    Name = "";
                    Indents = false;
                    Pattern.Clear();
                    throw ex;
                }
            }
        }


        public Production(string source)
        {
            Xml = source;
        }
    }


    public class PatternList : IEnumerable
    {
        private List<PatternItem> items = new List<PatternItem>();

        public void Add(PatternItem item)
        {
            items.Add(item);
        }

        public void Clear()
        {
            items.Clear();
        }

        public List<PatternItem> Items
        {
            get
            {
                return items;
            }
        }

        public int Length
        {
            get { return items.Count; }
        }

        public override string ToString()
        {
            string value = "";
            foreach (PatternItem item in items)
            {
                value += " " + item.ToString();
            }

            return value.Trim();
        }

        public void FromString(string source)
        {
            // create this pattern from the string
            if (Parse(source) != "")
            {
                throw new Exception("Error in pattern: " + source);
            }
        }

        internal string Parse(string source)
        {
            // remove leading/trailing spaces
            source = source.Trim();

            while (source != "")
            {
                int index;
                string token;

                // find the next token
                index = source.IndexOf(" ");
                if (index > 0)
                {
                    token = source.Substring(0, index - 1);
                    source = source.Substring(index + 1);
                }
                else
                {
                    token = source;
                    source = "";
                }

                PatternItem itm = new PatternItem();
                // parse this token
                switch (token[0])
                {
                    case ']':
                    case '|':
                    case ')':
                    case '}':
                        // finished this void-pattern
                        return token + " " + source;

                    case '\'':
                        itm.Type = PatternItem.ItemType.Operator;
                        itm.Value = token.Substring(1, token.Length - 2);
                        Add(itm);
                        break;

                    case '$':
                        itm.Type = PatternItem.ItemType.Production;
                        itm.Value = token.Substring(1);
                        Add(itm);
                        break;

                    case '!':
                        itm.Type = PatternItem.ItemType.Token;
                        itm.Value = token.Substring(1);
                        Add(itm);
                        break;

                    case '[':
                        itm.Type = PatternItem.ItemType.Option;
                        source = itm.Pattern.Parse(source);
                        // check and remove the closing brace
                        if (source[0] == ']')
                            source = source.Substring(1);
                        else
                            throw new Exception("Missing ] from option pattern");

                        Add(itm);
                        break;

                    case '{':
                        itm.Type = PatternItem.ItemType.Repeat;
                        source = itm.Pattern.Parse(source);
                        // check and remove the closing brace
                        if (source[0] == '}')
                            source = source.Substring(1);
                        else
                            throw new Exception("Missing } from repeat pattern");

                        Add(itm);
                        break;

                    case '(':
                        // parse alternation patterns
                        itm.Type = PatternItem.ItemType.Alternation;
                        do
                        {
                            PatternList voiditm = new PatternList();
                            // parse the next pattern
                            source = voiditm.Parse(source);
                            // add it to the alternates list
                            itm.Alternates.Add(voiditm);
                            // check what to do next
                            if (source[0] == ')')
                            {
                                // remove the closing brace
                                source = source.Substring(1);
                                // done
                                break;
                            }
                            if (source[0] != '|')
                            {
                                // this is wrong
                                throw new Exception("Expected | or ) in alternation pattern");
                            }
                            // remove the separator
                            source = source.Substring(1);
                        } while (true);
                        // add the item
                        Add(itm);
                        break;

                    default:
                        itm.Type = PatternItem.ItemType.Keyword;
                        itm.Value = token;
                        Add(itm);
                        break;
                }
            }
            // finished
            return "";
        }

        public PatternList()
        {
            Clear();
        }

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return items.GetEnumerator();
        }

        #endregion
    }

    public class PatternItem
    {

        public enum ItemType
        {
            Keyword = 0,   //   value
            Operator,      //  'value'
            Production,    //  $value
            Token,         //  !value
            Alternation,   //  ( pattern | pattern | pattern )
            Option,      //  [ pattern ]
            Repeat        //  { pattern }
        }

        public ItemType Type;
        public string Value;
        public PatternList Pattern = new PatternList();
        public List<PatternList> Alternates = new List<PatternList>();

        public void FromString(string source)
        {
            switch (source[0])
            {
                case '\'':
                    Type = ItemType.Operator;
                    Value = source.Substring(1, source.Length - 2);
                    break;
                case '$':
                    Type = ItemType.Production;
                    Value = source.Substring(1);
                    break;
                case '!':
                    Type = ItemType.Token;
                    Value = source.Substring(1);
                    break;
                case '[':
                    Type = ItemType.Option;
                    Pattern.FromString(source.Substring(1, source.Length - 2));
                    break;
                case '{':
                    Type = ItemType.Repeat;
                    Pattern.FromString(source.Substring(1, source.Length - 2));
                    break;
                case '(':
                    //' parse alternation patterns
                    Type = PatternItem.ItemType.Alternation;
                    do
                    {
                        PatternList voiditm = new PatternList();
                        // parse the next pattern
                        source = voiditm.Parse(source);
                        // add it to the alternates list
                        Alternates.Add(voiditm);
                        // check what to do next
                        if (source[0] == ')')
                        {
                            // remove the closing brace
                            source = source.Substring(1);
                            // done
                            break;
                        }
                        if (source[0] != '|')
                        {
                            // this is wrong
                            throw new Exception("Expected | or ) in alternation pattern");
                        }
                        // remove the separator
                        source = source.Substring(1);
                    } while (true);
                    break;
                default:
                    Type = ItemType.Keyword;
                    Value = source;
                    break;
            }
        }

        public override string ToString()
        {
            switch (Type)
            {
                case ItemType.Keyword:
                    return Value;

                case ItemType.Operator:
                    return "'" + Value + "'";

                case ItemType.Production:
                    return "$" + Value;

                case ItemType.Token:
                    return "!" + Value;

                case ItemType.Option:
                    return "[ " + Pattern.ToString() + " ]";

                case ItemType.Repeat:
                    return "{ " + Pattern.ToString() + " }";

                case ItemType.Alternation:
                    string value = "";
                    foreach (PatternList ptn in Alternates)
                    {
                        if (value == "")
                            value = ptn.ToString();
                        else
                            value += " | " + ptn.ToString();
                    }
                    return "( " + Value + " )";

                default:
                    return "#ERR#";
            }
        }

    }

}
